/*
* Copyright (C) 2019 ~ 2021 Uniontech Software Technology Co.,Ltd.
*
* Author:     yanghongwei <yanghongwei@uniontech.com>
*
* Maintainer: yanghongwei <yanghongwei@uniontech.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Fanotify.h"

#include <errno.h>
#include <inttypes.h>
#include <fcntl.h>
#include <linux/limits.h>
#include <iostream>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <sys/fanotify.h>

Fanotify::Fanotify()
{
}

Fanotify::~Fanotify()
{
    close(fanFd) ;
}

//设置监控对象为目录下的子文件
void Fanotify::setNotifyObject(std::string path)
{
    this->paths = path;
    //初始化，第一个参数为FAN_CLASS_CONTENT（表示允许接收通知文件事件）另一个值为FAN_CLASS_NOTIF 为默认值。只允许接收事件通知，在一般的监听中，使用FAN_CLASS_CONTENT
    //第二个参数，表示接收到操作文件事件后，本程序对文件的操作为可读可写，因为metadata中的fd为程序操作发生事件的文件的fd，可以直接操作那个文件，操作完后，要关掉。
    fanFd = fanotify_init(FAN_CLASS_PRE_CONTENT, O_RDWR);
    if (fanFd < 0) {
        std::cout << __FILE__ << "   " << __LINE__ << "     " << strerror(errno)<< std::endl;
        return;
    }
    std::cout << "初始化完成" << std::endl;
    uint64_t fanMask = 0;
    //设置在文件open的时候，会通知本程序，同意访问的话，本程序需要给内核发允许访问标志，然后其他程序才能继续访问，否则不能访问
    fanMask |= FAN_ALL_PERM_EVENTS;
    //FAN_EVENT_ON_CHILD 作用于当前操作目录的所有子文件
    //FAN_MARK_ADD 添加监听事件标志的标志
    int ret = fanotify_mark(fanFd, FAN_MARK_ADD, fanMask | FAN_EVENT_ON_CHILD | FAN_CLOSE | FAN_OPEN | FAN_ACCESS | FAN_MODIFY,
                            AT_FDCWD, path.c_str());
    if (ret < 0) {
        std::cout << __LINE__ <<"   " __FILE__ << "   " << strerror(errno) << std::endl;
    }
    std::cout << "对象处理完成"<< std::endl ;
}

int Fanotify::getNotifyFD()
{
    return fanFd ;
}

void Fanotify::startListen()
{
    char buf[4096];
    int len = 0;
    fd_set rfd;
    //使用select监听
    FD_ZERO(&rfd);
    FD_SET(fanFd, &rfd);
    std::cout << "开始监听" << std::endl;
    selectEvent(&rfd);
    std::cout << "发生事件" << std::endl;
    while ((len = read(fanFd, buf, sizeof(buf))) > 0) {
        struct fanotify_event_metadata *metadata;
        metadata = (fanotify_event_metadata *)buf;
        if (metadata->fd >= 0) {
            getEvent(metadata, len);
            std::cout << "检测完成" << std::endl;
        }
        selectEvent(&rfd) ;
    }
    std::cout << strerror(errno) << std::endl;
}

int Fanotify::selectEvent(fd_set *rfd)
{
   std::cout << "阻塞" << std::endl;
    while (select(fanFd + 1, rfd, NULL, NULL, NULL) < 0) {
        if (errno != EINTR) {
            std::cout << __LINE__ <<  "     " << std::endl;
            exit(0) ;
        }
    }
    return 1 ;
}

int Fanotify::getEvent(const struct fanotify_event_metadata *metadata, int len)
{
    while (FAN_EVENT_OK(metadata, len)) {
        //处理matadata
        if (metadata->mask & FAN_OPEN) {
            std::cout << "文件" << getFileName(metadata->fd) << "被打开" << getProcessName(metadata->pid) << std::endl;
        }

        if (metadata->mask & FAN_CLOSE) {
            if (metadata->mask & FAN_CLOSE_WRITE) {
                std::cout << "文件" << getFileName(metadata->fd) << "写关闭" << std::endl;
            }

            if (metadata->mask & FAN_CLOSE_NOWRITE) {
                std::cout << "文件" << getFileName(metadata->fd) << "关闭操作" << std::endl;
            }
        }

        if (metadata->mask & FAN_MODIFY) {
            std::cout << "修改" << getFileName(metadata->fd) << std::endl;
        }

        printf("metadata->mask:%x  %d\n", metadata->mask, metadata->fd);
        if (metadata->mask & FAN_ALL_PERM_EVENTS) {
//            std::cout << "给内核发送消息" << std::endl;
            handlePerm(metadata) ;
        }

        close(metadata->fd);
        metadata = FAN_EVENT_NEXT(metadata, len) ;
    }
    return 1;
}

void Fanotify::detectOpenClose()
{
//    std::cout << "使用提供的系统调用，设置开关标志" << std::endl ;
    int ret = fanotify_mark(fanFd, FAN_MARK_ADD, FAN_CLOSE|FAN_OPEN|FAN_ACCESS|FAN_MODIFY, AT_FDCWD, paths.c_str()) ;
    if (ret < 0) {
        std:: cout << __FILE__ << "    " << __LINE__ << std:: endl ;
    }
}

std::string Fanotify::getFileName(int fd)
{
    /* Retrieve and print pathname of the accessed file. */
    std::string paths ;
    char path[PATH_MAX];
    ssize_t path_len;
    char procfd_path[PATH_MAX];
    snprintf(procfd_path, sizeof(procfd_path), "/proc/self/fd/%d", fd);
    path_len = readlink(procfd_path, path,
                        sizeof(path) - 1);
    if (path_len == -1) {
        perror("readlink");
        exit(EXIT_FAILURE);
    }

    path[path_len] = '\0';
    return std::string(path);
}

std::string Fanotify::getProcessName(int pid)
{
    char buf[521] = { 0 };
    sprintf(buf, "grep 'Name:' /proc/%d/status |awk '{print($2)}'", pid);
    /*执行预先设定的命令，并读出该命令的标准输出*/
    FILE *fp = popen(buf, "r");
    if (NULL == fp) {
        perror("popen执行失败！");
        return "unknown";
    }

    memset(buf, 0, sizeof(buf));
    bool isGet = (fgets(buf, sizeof(buf), fp) != NULL);

    /*等待命令执行完毕并关闭管道及文件指针*/
    pclose(fp);

    return isGet ? buf : "unknown";
}

int Fanotify::handlePerm(const struct fanotify_event_metadata *metadata) {
    struct fanotify_response response_struct;
    int ret;
    response_struct.fd = metadata->fd;
    response_struct.response = FAN_ALLOW;

    ret = write(fanFd, &response_struct, sizeof(response_struct));
    if (ret < 0)
        return ret;

    return 0;
}
